package com.theeviljames.coursework.ann;

import java.util.Arrays;
import java.util.Random;
import com.theeviljames.coursework.exceptions.MatrixException;


/**
 * General matrix toolbox for ANN's and GA's
 * @author James McTaggart
 *
 */
public final class ANNMatrix {

	public static double[][] add(double[][] a, double[][] b) throws MatrixException{
		if(a.length!=b.length||a[0].length!=b[0].length)throw new MatrixException("Matrix dimensions must agree for addition\n" 
				+ "Matrix a>" + a.length + ","+a[0].length + " Matrix b>" + b.length + "," + b[0].length);
		double[][] c = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				c[i][j] = a[i][j] + b[i][j];
			}
		}
		return c;
	}

	public static double[][] random(int r, int c, double scale, Random rand){
		double[][] a = new double[r][c];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				a[i][j] =scale*((rand.nextDouble()*2)-1.0);
			}
		}
		return a;
	}
	
	public static double[][] subtract(double[][] a, double[][] b) throws MatrixException{
		if(a.length!=b.length||a[0].length!=b[0].length)throw new MatrixException("Matrix dimensions must agree for addition");
		double[][] c = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				c[i][j] = a[i][j] - b[i][j];
			}
		}
		return c;
	}
	
	public static double[][] times(double[][] a, double[][] b) throws MatrixException{
		if(a[0].length!=b.length)throw new MatrixException("If A is n by m and B is m by p then the m dimensions must be the same\n"
				+ "Matrix a>" + a.length + ","+a[0].length + " Matrix b>" + b.length + "," + b[0].length);
		double[][] c = new double[a.length][b[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < b[0].length; j++){
				double total = 0.0;
				for(int k = 0; k < a[0].length; k++){
					total += a[i][k]*b[k][j];
				}
				c[i][j] = total;
			}
		}
		return c;
	}
	
	public static double[][] scalarTimes(double[][] a, double scalar){
		double[][] b = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				b[i][j] = a[i][j]*scalar;
			}
		}
		return b;
	}
	
	public static double[][] oneMinus(double[][] a){
		double[][] b = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				b[i][j] = 1-a[i][j];
			}
		}
		return b;
	}

	public static double[][] elemTimes(double[][] a, double[][] b) throws MatrixException{
		if(a.length!=b.length||a[0].length!=b[0].length)throw new MatrixException("For element by element matrix multiplication the matrix dimensions must agree\n"
				+ "Matrix a>" + a.length + ","+a[0].length + " Matrix b>" + b.length + "," + b[0].length);
		double[][] c = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				c[i][j] = a[i][j]*b[i][j];
			}
		}
		return c;		
	}
	
	public static double[][] transpose(double[][] a){
		double[][] b = new double[a[0].length][a.length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				b[j][i] = a[i][j];
			}
		}
		return b;
	}
	
	public static double[][] trimVertical(double[][] a){
		double[][] b = new double[a.length-1][];
		for(int i = 0; i < b.length; i++)b[i] = a[i];
		return b;
	}
	
	public static double[][] trimHorizontal(double[][] a){
		double[][] b = trimVertical(transpose(a));
		return transpose(b);
	}
	
	public static double[][] sigmoid(double[][] a){
		double[][] b = new double[a.length][a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				b[i][j] = sig(a[i][j]);
			}
		}
		return b;
	}
	
	private static double sig(double a){
		return 1/(1+Math.exp(-a));
	}
	
	public static double[] flatten(double[][] a){
		double[] b = new double[a.length*a[0].length];
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				int k = (i*a[0].length)+j;
				b[(i*a[0].length)+j] = a[i][j];
			}
		}
		return b;
	}

	public static double[][] unFlatten(double[] a, int x, int y){
		double[][] b = new double[x][y];
		for(int i = 0; i < x; i++){
			for(int j = 0; j < y; j++){
				b[i][j] = a[(i*b[0].length)+j];
			}
		}
		return b;
	}
	
	public static double[][][] crossover(double[][] a, double[][] b, int crossover){
		double[][][] c = new double[2][][];
		double[] flatA = flatten(a);
		double[] flatB = flatten(b);
		double[] newFlatA = new double[flatA.length];
		double[] newFlatB = new double[flatB.length];
		System.arraycopy(flatA, 0, newFlatB, 0, crossover);
		System.arraycopy(flatB, 0, newFlatA, 0, crossover);
		System.arraycopy(flatA, crossover, newFlatA, crossover, flatA.length-crossover);
		System.arraycopy(flatB, crossover, newFlatB, crossover, flatA.length-crossover);
		c[0] = unFlatten(newFlatA,a.length,a[0].length);
		c[1] = unFlatten(newFlatB,a.length,a[0].length);
		return c;
	}
	
	public static boolean equal(double[][] a, double[][] b) throws MatrixException{
		if(a.length!=b.length||a[0].length!=b[0].length)throw new MatrixException("For element by element matrix multiplication the matrix dimensions must agree\n"
				+ "Matrix a>" + a.length + ","+a[0].length + " Matrix b>" + b.length + "," + b[0].length);
		boolean result = true;
		for(int i = 0; i < a.length; i++){
			for(int j = 0; j < a[0].length; j++){
				if(a[i][j]!=b[i][j]){
					result = false;
					break;
				}
			}
		}
		return result;
	}
	
	public static void print(double[][] a){
		for(double[] b:a)System.out.println(Arrays.toString(b));
		System.out.println();
	}
	
	public static void print(double[][] a, String legend){
		System.out.println(legend + "\n");
		print(a);
	}
}
